Skip to content

feat(protocol): 7.15 release additions — hive signing (1614-1617), clearsign identity icons, thorchain denom, ripple memo, zcash - #112

Open
BitHighlander wants to merge 72 commits into
masterfrom
up/release-protocol
Open

feat(protocol): 7.15 release additions — hive signing (1614-1617), clearsign identity icons, thorchain denom, ripple memo, zcash#112
BitHighlander wants to merge 72 commits into
masterfrom
up/release-protocol

Conversation

@BitHighlander

@BitHighlander BitHighlander commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Upstream branch → upstream master. keepkey:up/release-protocol is the single protocol PR for the firmware 7.15 / RC18 release line. It replaces the mis-structured #111 and remains blocked on human review.

Additions

All changes outside Zcash are additive; existing field numbers and wire behavior remain compatible. The Zcash surface is a breaking change — see "Zcash wire protocol v2" below.

  • Hive signing: HiveSignMessage / HiveSignedMessage (1614/1615) and HiveSignOperations / HiveSignedOperations (1616/1617). 1610-1613 are allocated to NEAR as schema only: messages-near.proto and messages-near.options define the intended wire surface, but firmware 7.15 / RC18 ships no NEAR handler and answers those requests with Failure_UnexpectedMessage. Hosts must not advertise NEAR support on the basis of these bindings.
  • Clearsign identity icons on LoadClearsignSigner: bounded RLE icon data, icon_width, icon_height, and the legacy-compatible persist field.
  • Earlier 7.x additions accumulated on this release branch: thorchain any-denom, Ripple memo, and Zcash messages.

Zcash wire protocol v2 (BREAKING)

Zcash shielded support shipped as a preview with no stable host implementations, so the flow was corrected in place rather than duplicated behind new message IDs. A host written against firmware <= 7.14.x will not interoperate with 7.15 and must be updated. The full migration is documented at the top of messages-zcash.proto:

  1. MessageType 1307 renamed ZcashTransparentSig -> ZcashTransparentSigned and reshaped from one signature per request to a batched repeated bytes signatures. The wire ID is unchanged, so a v1 host decodes 1307 into the wrong message.
  2. New ZcashTransparentOutput (1310) and ZcashTransparentAck (1311). Outputs stream before inputs and all inputs are buffered before any signature, because ZIP-244 per-input transparent sighashes commit to every transparent prevout, value, script, sequence and output. ZcashTransparentInput.sighash is demoted to optional and rejected when present.
  3. ZcashDisplayAddress fields 3-6 (address, ak, nk, rivk) removed and reserved; the device derives and displays its own Orchard UA. Either account or a full address_n is now required.
  4. ZcashPCZTAction.is_spend is required, and ZcashSignedPCZT.signatures is compact — a v1 host indexing signatures by action index reads the wrong signature.
  5. ZcashSignPCZT.sapling_digest is reserved for future Sapling support and rejected when set.

RC18 Zcash wire contract

The regular/full RC18 firmware includes Orchard privacy. Only bitcoin-only compiles non-Bitcoin features out.

  • all Orchard actions are streamed to and validated by the device

  • is_spend is required by firmware 7.15

  • dummy shield/change actions use is_spend=false and receive no RedPallas signature

  • real spends use is_spend=true

  • ZcashSignedPCZT.signatures is compact: exactly one 64-byte signature per real spend, in ascending action order

  • an all-dummy shield transaction therefore returns zero Orchard signatures

  • shielded_pool / ironwood_digest (tags 19-20) and ZCASH_SHIELDED_POOL_IRONWOOD are schema only: RC18 is Orchard-only and rejects a non-Orchard pool or a present ironwood_digest with Failure. The tags are allocated so they cannot be reused; no host should read them as supported behavior.

These semantics are documented on the protocol fields and pinned by a machine-readable contract check.

Validation

Review and merge gates

  • singular master-targeting protocol branch
  • hosted descriptor and RC18 contract validation
  • human approval and merge of this PR
  • Python #197 repinned to the resulting canonical master commit, reviewed, and merged
  • firmware #320 repinned to both canonical dependency merge commits and rerun

No merge, tag, or release is requested before human review.

BitHighlander and others added 30 commits March 23, 2026 15:26
- PCZT streaming protocol: ZcashSignPCZT, ZcashPCZTAction, ZcashPCZTActionAck
- Orchard FVK: ZcashGetOrchardFVK, ZcashOrchardFVK
- Transparent shielding: ZcashTransparentInput, ZcashTransparentSig
- Wire IDs 1300-1307
- nanopb options for all fields

Multi-phase protocol: session init → action streaming → transparent signing.
Supports on-device Orchard digest verification via sub-digest fields.
New messages for displaying a Zcash unified address on the device screen
with FVK verification. The host provides the UA string and FVK components;
the device independently derives FVK from seed and verifies the match
before displaying the address with a QR code.

- ZcashDisplayAddress (wire_in 1308): address + ak/nk/rivk for verification
- ZcashAddress (wire_out 1309): confirmed address after user approval
- nanopb options: address max_size:128, key fields max_size:32
feat(zcash): add ZcashDisplayAddress protocol (IDs 1308/1309)
The device only verifies the Orchard FVK — it cannot verify
transparent or Sapling receivers that may also be bundled in
a Unified Address. Updated proto comments to explicitly state
the guarantee: "This UA contains an Orchard receiver from this
account" rather than implying full address ownership.

Also clarified that account or address_n is required (no silent
fallback to account 0).
docs(zcash): clarify ZcashDisplayAddress verification scope
…TIP-712)

Adds proto definitions for TRON message-signing parity:
- TronSignMessage / TronMessageSignature (1404/1405) — TIP-191 personal_sign
- TronVerifyMessage (1406) — host-asserted signature verification
- TronSignTypedHash / TronTypedDataSignature (1407/1408) — TIP-712 hash mode

Mirrors the Ethereum personal_sign + EIP-712 hash-mode shape. Firmware
implementation will reuse the secp256k1 + keccak256 primitives already
present for Ethereum, swapping the message prefix to '\x19TRON Signed Message:\n'
for TIP-191 and using '\x19\x01' for TIP-712.

Reserves IDs 1404-1408 contiguous to existing TRON range (1400-1403).
Adds TonSignMessage / TonMessageSignature (1504/1505) — basic Ed25519
arbitrary-bytes signing, mirroring SolanaSignMessage's shape.

This primitive lacks domain separation by design (raw Ed25519 over message
bytes). Firmware should gate it behind the AdvancedMode policy — same
fence used for SolanaSignMessage in fsm_msg_solana.h — until a TON
Connect ton_proof envelope is added as a separate proto.

Reserves IDs 1504-1505 contiguous to existing TON range (1500-1503).
Adds SolanaSignOffchainMessage / SolanaOffchainMessageSignature (756/757)
implementing the Solana off-chain message spec:

  '\xff' || 'solana offchain' || version || format || length || message

The '\xff' lead byte is invalid as a Solana transaction prefix, providing
the domain separation that plain SolanaSignMessage (754/755) lacks. With
this primitive, firmware can drop the AdvancedMode policy gate currently
required for SolanaSignMessage (fsm_msg_solana.h:461-472) for ASCII/UTF8
off-chain messages, since the envelope makes transaction-shaped attacks
impossible.

message_format values per spec:
  0 = Restricted ASCII (max 1212 bytes) — display-renderable
  1 = UTF-8 limited (max 1212 bytes) — display-renderable with care
  2 = UTF-8 extended (max 65515) — blind-sign only

Reserves IDs 756-757 contiguous to existing Solana range (750-755).
Bumped message max_size to 1212 to match the spec ceiling for formats 0/1.
…sages

ZIP-32 §6.1 seed fingerprint:

  SeedFingerprint := BLAKE2b-256("Zcash_HD_Seed_FP", seed)

A 32-byte stable identity of the device's seed. Adds optional bytes
seed_fingerprint fields across the existing zcash messages so hosts
and devices can bind FVKs, addresses, and signing sessions to a
specific seed identity.

Four new fields, all optional, fully backward compatible:

  ZcashOrchardFVK.seed_fingerprint              (4)
    Returned alongside (ak, nk, rivk). Lets a host pin an FVK to
    this device's seed.

  ZcashAddress.seed_fingerprint                 (2)
    Returned alongside the confirmed UA after on-device verification.
    Lets a host record "this address is on this device's seed."

  ZcashSignPCZT.expected_seed_fingerprint       (31)
    Sent by host. If present, device checks against its own
    fingerprint and rejects with Failure on mismatch before signing.
    Mirrors Keystone3's PCZT zip32_derivation seed_fingerprint check
    at the session level (one tx = one seed, no per-action duplication
    needed for our flow).

  ZcashDisplayAddress.expected_seed_fingerprint (7)
    Sent by host. Same rejection semantics as above before displaying.

Matching nanopb max_size:32 entries added to messages-zcash.options.

No existing fields modified. Devices and hosts that don't populate
the new fields continue to work unchanged.
feat(zcash): add seed_fingerprint binding to FVK / address / sign messages
Brings in upstream's 7.14.0 release + features/7.15 commits:
  d0b8d80 feat: 7.14.0 protocol — BIP-85, EVM metadata, Solana, TRON, TON, Zcash
  18bb4a7 Merge pull request #100 from keepkey/release/7.14.0
  bbcfcb0 feat: add ZcashDisplayAddress protocol messages (IDs 1308-1309)
  bf8646b Merge pull request #101 from keepkey/features/7.15

Note: ZcashDisplayAddress was developed in parallel on both sides.
Conflict resolution prefers upstream's version (max_size:256 for the
unified-address fields, more correct than fork's :128 since UAs can be
long when shielded receivers are present).

# Conflicts:
#	messages-zcash.options
#	messages-zcash.proto
#	messages.proto
#	package.json
Brings in TRON/TON/Solana message-signing proto definitions:
  0e3dc97 feat(tron): TIP-191 SignMessage, VerifyMessage, TIP-712 SignTypedHash
  20e646a feat(ton): Ed25519 SignMessage primitive
  c0ef415 feat(solana): SignOffchainMessage with domain-separated envelope

After this merge, fork master is the single source of truth that
firmware branches pin to during fork-only testing. A clean upstream
PR can be assembled later by cherry-picking these 3 commits onto a
fresh branch off keepkey/master.
Field comments documented the formula as

  BLAKE2b-256("Zcash_HD_Seed_FP", seed)

but ZIP-32 §6.1 (and the actual conforming implementations in the
upstream zip32 Rust crate, keystone3-firmware, and our own firmware)
prepend a 1-byte length:

  BLAKE2b-256("Zcash_HD_Seed_FP", I2LEBSP_8(len(seed)) || seed)

A host implementer following the proto comments would compute the
wrong fingerprint and have the device reject every signing/display
request with "seed fingerprint mismatch."

Comment-only change. No wire-format impact.
…formula-doc

fix(zcash): correct seed_fingerprint formula in proto comments
Remove fields 3-6 (address, ak, nk, rivk) from ZcashDisplayAddress.
Field numbers are reserved to prevent reuse. The on-device UA
derivation (Sinsemilla + SWU hash-to-curve) shipped — FVK-match
attestation against a host-built UA is strictly weaker than
device-derived display and is no longer supported.

What stays: address_n / account / expected_seed_fingerprint.
What ZcashAddress returns: address (now device-derived) + seed_fingerprint.
Add optional string memo field (field 7) to RippleSignTx protobuf message.
This enables THORChain swap routing memos and other arbitrary memo data
to be included in XRP transactions signed by the device.
feat(ripple): add memo field to RippleSignTx for 7.14.2
Merges upstream 7.14.1 (Tron TIP-191/712, TON SignMessage, Solana
SignOffchainMessage) with fork's Zcash UA/seed_fingerprint/display
address additions. Conflict resolution: kept upstream's format-2
removal from SolanaSignOffchainMessage docs.
Adds messages-hive.proto with HiveGetPublicKey, HivePublicKey,
HiveSignTx, and HiveSignedTx. Assigns message type IDs 1600-1603
in messages.proto.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* release: device-protocol 7.14.1

* feat(tron): add SignMessage (TIP-191), VerifyMessage, SignTypedHash (TIP-712)

Adds proto definitions for TRON message-signing parity:
- TronSignMessage / TronMessageSignature (1404/1405) — TIP-191 personal_sign
- TronVerifyMessage (1406) — host-asserted signature verification
- TronSignTypedHash / TronTypedDataSignature (1407/1408) — TIP-712 hash mode

Mirrors the Ethereum personal_sign + EIP-712 hash-mode shape. Firmware
implementation will reuse the secp256k1 + keccak256 primitives already
present for Ethereum, swapping the message prefix to '\x19TRON Signed Message:\n'
for TIP-191 and using '\x19\x01' for TIP-712.

Reserves IDs 1404-1408 contiguous to existing TRON range (1400-1403).

* feat(ton): add SignMessage Ed25519 message-signing primitive

Adds TonSignMessage / TonMessageSignature (1504/1505) — basic Ed25519
arbitrary-bytes signing, mirroring SolanaSignMessage's shape.

This primitive lacks domain separation by design (raw Ed25519 over message
bytes). Firmware should gate it behind the AdvancedMode policy — same
fence used for SolanaSignMessage in fsm_msg_solana.h — until a TON
Connect ton_proof envelope is added as a separate proto.

Reserves IDs 1504-1505 contiguous to existing TON range (1500-1503).

* feat(solana): add SignOffchainMessage with domain-separated envelope

Adds SolanaSignOffchainMessage / SolanaOffchainMessageSignature (756/757)
implementing the Solana off-chain message spec:

  '\xff' || 'solana offchain' || version || format || length || message

The '\xff' lead byte is invalid as a Solana transaction prefix, providing
the domain separation that plain SolanaSignMessage (754/755) lacks. With
this primitive, firmware can drop the AdvancedMode policy gate currently
required for SolanaSignMessage (fsm_msg_solana.h:461-472) for ASCII/UTF8
off-chain messages, since the envelope makes transaction-shaped attacks
impossible.

message_format values per spec:
  0 = Restricted ASCII (max 1212 bytes) — display-renderable
  1 = UTF-8 limited (max 1212 bytes) — display-renderable with care
  2 = UTF-8 extended (max 65515) — blind-sign only

Reserves IDs 756-757 contiguous to existing Solana range (750-755).
Bumped message max_size to 1212 to match the spec ceiling for formats 0/1.

* feat(zcash): drop host-supplied UA from ZcashDisplayAddress

Remove fields 3-6 (address, ak, nk, rivk) from ZcashDisplayAddress.
Field numbers are reserved to prevent reuse. The on-device UA
derivation (Sinsemilla + SWU hash-to-curve) shipped — FVK-match
attestation against a host-built UA is strictly weaker than
device-derived display and is no longer supported.

What stays: address_n / account / expected_seed_fingerprint.
What ZcashAddress returns: address (now device-derived) + seed_fingerprint.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* docs: update SolanaSignOffchainMessage to reflect 1212 byte limit and drop format 2

Agent-Logs-Url: https://github.com/keepkey/device-protocol/sessions/880cd954-b4b2-4f87-af05-8d715e1e0dc4

Co-authored-by: pastaghost <62026038+pastaghost@users.noreply.github.com>

* feat(hive): add Hive blockchain message definitions

Adds messages-hive.proto with HiveGetPublicKey, HivePublicKey,
HiveSignTx, and HiveSignedTx. Assigns message type IDs 1600-1603
in messages.proto.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(hive): add messages-hive.proto to build, drop broken build:json step

build:json used pbjs v0.0.5 which cannot parse proto3 reserved fields (present
in zcash, cosmos, ethereum, etc). proto.json is unused by the vault — only
messages_pb.js is imported. Build now runs build:js + build:postprocess only.

* feat(hive): add HiveGetPublicKeys, HiveSignAccountCreate, HiveSignAccountUpdate + SLIP-0048 paths

---------

Co-authored-by: pastaghost <62026038+pastaghost@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves conflict between:
- feature/hive branch (2b25cf7): 10 Hive message types
- alpha branch (c1dea44): Zcash clear-signing + THORChain denom field

Kept all 10 Hive message types; added alpha's Zcash/THORChain proto additions.
NearGetAddress, NearAddress, NearSignTx, NearSignedTx.
Ed25519 derivation m/44'/397'/0'. Implicit account = lowercase hex of
the 32-byte Ed25519 pubkey.

Numbered 1610-1613 to avoid colliding with Hive (1600-1609), which
landed on alpha after the original NEAR spike.
feat(near): NEAR Protocol proto definitions (MessageType 1610-1613)
…oadcast (#35)

account_create is authorized on-chain by the creator (sponsor) account's active
authority, not by the new account. The device's owner-key signature here is a
proof-of-control attestation; the sponsor recovers the owner pubkey to verify
control, then rebuilds and signs the real account_create with the creator's
active key server-side. Comment-only — no wire or generated-code change.

Addresses review P1 on the (closed) feature/hive PR.
Adds the device-protocol messages required by the upcoming firmware release:

- thorchain: ThorchainMsgSend.denom (field 11) — non-RUNE assets (TCY, RUJI, IBC)
- ripple:    RippleSignTx.memo (field 7) — XRP->THORChain swap routing
- hive:      full Hive support — HiveGetPublicKey(s), HiveSignTx,
             HiveSignAccountCreate/Update (MessageType 1600-1609)
- zcash:     clear-signing + Orchard shielded protocol (transparent in/out/ack,
             PCZT, FVK, display-address)

All additions are new optional fields / new message types — additive and
backward-compatible. lib/ bindings are gitignored build artifacts; package.json
build:js/json updated to include messages-hive.proto.
BitHighlander and others added 4 commits August 2, 2026 18:38
…taging

feat(features): advertise Taproot signing support
ResetDevice.dice_entropy=10 asks the device to collect dice rolls with
the single button and mix them into the internal entropy before it is
displayed or committed. DebugLinkDecision.input=2 lets debug builds
inject synthetic roll input (kept short so the decoded struct fits the
firmware tiny-message buffer). DebugLinkState.dice_digest=15 exposes
SHA-256 of the collected ASCII roll string so tests can prove the
device received exactly the injected rolls. ButtonRequest_DiceRoll=39
announces the entry screen to the host.
feat(reset): dice-entropy fields for on-device roll collection
BitHighlander and others added 9 commits August 4, 2026 22:38
Picks up f2c3c00 (ci: auto-request Copilot review), the only commit the
fork lacked. The fork's 28 protocol commits -- zcash Orchard + clear-sign,
hive, near, tron/ton/solana message signing, ripple memo, thorchain denom --
are unaffected; this adds one workflow file and nothing else.
yarn install --frozen-lockfile fails on this branch with:
  @mapbox/node-pre-gyp@2.0.3: The engine "node" is incompatible with this
  module. Expected version ">=18". Got "17.2.0"

The unpinned legacy circleci/node image resolves to node 17.2.0. master
passes only because its older lockfile predates that transitive
requirement; any lockfile refresh trips it. Pin a maintained image.
The Ironwood enum was inserted above the file-level java_package /
java_outer_classname options, leaving them after a top-level definition.
protobufjs's parser rejects that outright:

  Error: illegal token 'option' (messages-zcash.proto, line 16)

so 'npm run build' (build:json -> lib/proto.json) fails on this branch.
protoc accepts either order, so the generated descriptors are unchanged;
every other proto in the repo already declares options directly after
syntax. Verified: pbjs now emits proto.json cleanly.
alpha pinned device-protocol at cf308fd, 32 commits ahead of fork master and
29 behind. Per the branch SOP alpha must pin fork MASTERS, not loose commits --
a pin that exists only on a feature branch is the tech debt, because nothing
downstream can resolve it without knowing which branch it came from.

Merged rather than picked, since both lines had real work:

  messages.proto        NEAR 1610-1613 (master) + Hive 1614-1617 and Clearsign
                        attestor 1700-1703 (alpha). No ID collision -- alpha's
                        side already carried the comment reserving 1610-1613 for
                        NEAR, so this merge was anticipated.
  messages-zcash        alpha's shielded_pool and ironwood_digest (NU6.3 / v6)
  messages-hive         alpha's SignMessage/SignedMessage options; master's
                        account_create attestation docs kept
  package.json          alpha's scripts as the base: they actually run build:json,
                        and use grpc_tools_node_protoc explicitly rather than bare
                        protoc. Master contributed messages-near.proto, which alpha
                        lacked. All three of near/zcash/hive now appear in both
                        build:js and build:json.

Verified no duplicate field numbers within any message, and no duplicate
MessageType IDs.
A Solana v0 transaction may source instruction accounts from an Address Lookup
Table. Those accounts are NOT in the bytes being signed, so the device cannot
derive them and refuses to guess: the transaction is forced to
SOL_TX_REVIEW_OPAQUE, which fsm_msg_solana.h refuses outright unless the user
has enabled AdvancedMode, and which is then an explicit BLIND SIGN. The
instruction's meaning is never shown.

So this does not rescue a blank screen -- nothing is signed silently. It
upgrades a BLIND SIGN into a provider-attested CLEAR SIGN, which is the whole
point of the tier.

KKSOLSC1 schemas cannot close this: they are instruction-scoped and reusable,
carry no transaction hash, and work by decoding values out of the bytes the
device is signing. With a lookup table those bytes do not contain the accounts.

So a provider attests the resolved account list for THIS transaction:

  preimage = "KeepKeySolanaTxAccounts/1" || message_hash(32)
             || count(le32) || account[i](32) ...

Bound to the exact message hash, so it cannot be replayed onto another
transaction. Domain-tagged, so a signature made for any other purpose -- an EVM
metadata blob, a token definition -- cannot be replayed as one.

Annotation, not authority: accounts render as PROVIDER-ATTESTED next to the
provider alias, and the unverified-transaction review still runs.

Uses tags 5-7 of the reservation that named this descriptor. Tag 8 stays
reserved for one-request opaque-signing consent.
feat(solana): KKSOLSW1 transaction-bound account attestation on tags 5-7
Five messages (1704-1708) that let the device drive an EIP-712 walk instead of
being handed the document.

The device asks for one type definition, or one leaf VALUE, at a time, and
hashes each value in the same pass that displays it. The host owns the
document; the device holds only the digest stack for the containers currently
open. A 10,000-element array costs the same RAM as a 2-element one, and there
is no document-size limit to raise later.

This replaces Ethereum712TypesValues, which shipped the whole thing as two
2048-byte JSON blobs and was withdrawn in 7.14.2 because its parser could not
guarantee the value on screen was the value being hashed. Here that is
structural rather than reviewed: a value is displayed and absorbed from the
same buffer in the same call, and each member_path is requested exactly once.

That last clause is not incidental. Trezor shipped this same protocol with a
hole until 2.12.0 -- nothing bound repeated answers for one path to each other,
so a host could answer the domain name one way for the summary screen and
another for the hashing pass. Requesting each path once closes it by
construction rather than by caching around it.

ONE deliberate divergence from Trezor and OneKey: arrays.

Both describe an array as a field whose entry_type is another EthereumFieldType
-- a self-referential message. Trezor can, because core is Python. OneKey does
it on nanopb by compiling that one field as a POINTER (PB_ENABLE_MALLOC) and
then flattening the pointer chain into a fixed pool to sever the recursion.

KeepKey's nanopb is static-allocation only, and a heap inside a signing device
is not a liability worth taking on for one field. So array nesting is FLATTENED
onto the wire the way Ledger describes it: data_type is always the LEAF type,
and array_levels carries the dimensions in written order, 0 for dynamic:

    uint256        -> UINT,   size=32,               array_levels=[]
    address[]      -> ADDRESS,                       array_levels=[0]
    Person[3]      -> STRUCT, struct_name="Person",  array_levels=[3]
    int16[2][][4]  -> INT,    size=2,                array_levels=[2,0,4]

Nothing EIP-712 permits is lost and the encoding is bounded, flat and
statically sized. Enum values still match Trezor's so a shared host keeps its
mapping; ARRAY is reserved and never sent.

Values arrive as raw big-endian bytes of the declared width, not JSON. That
deletes the whole decimal-parsing step from the device -- and with it the
64-bit integer ceiling that made the old path refuse an unlimited approval,
which is the most common permit there is.

Validated with protoc 3.5.1 in kktech/firmware:v8.
…aming

feat(eip712): device-driven field streaming for structured typed data
up/release-protocol carried two build fixes that exist only on this
upstream branch -- the cimg/node:20.11 CI pin and moving the zcash file
options into the preamble so pbjs can parse them -- while 34 commits of
7.15 protocol work sat on the fork's master. develop cannot pin the fork
commit, because a PR into upstream carries its submodule pins and a
reviewer cannot resolve one that exists only on a fork.

Merged rather than rebased so nothing is force-pushed over an open PR,
and so both histories stay readable.

Verified after the merge, because a clean auto-merge keeps hunks from
both sides and that is exactly how one side quietly loses:
  - cimg/node:20.11 still pinned in .circleci/config.yml
  - the zcash file options are still in the preamble
  - EthereumSignTypedData / EthereumTypedDataStructAck /
    EthereumTypedDataValueAck all present in messages-ethereum.proto
BitHighlander added a commit to keepkey/keepkey-firmware that referenced this pull request Aug 22, 2026
keepkey/python-keepkey#216 -- the 7.14.2 disclosure tests -- is merged, so
upstream master is 598097e1 and carries them. This PR pinned 87ff2499,
that PR's branch head, which was the right pin while it was open and is
the wrong one now: a merged PR's branch can be deleted, and a pin at a
branch head rather than at master is exactly what a reviewer cannot
resolve later.

598097e1 is 1 ahead of 87ff2499 and 0 behind -- the merge commit, nothing
else. Every submodule pin on this branch was re-verified to FETCH from its
real URL, not merely to be well-formed.

deps/device-protocol stays at f2c3c005, which IS upstream master: the
7.14.2 line needs no protocol change. The 7.15 protocol additions are
keepkey/device-protocol#112 and land with that release.
@BitHighlander

Copy link
Copy Markdown
Collaborator Author

Review guide

Scope: the 7.15 protocol additions. This is a dependency merge — nothing here ships to a device by itself; it makes submodule pins resolvable for the firmware releases that follow.

The change is wire-additive

The only deletion in messages.proto is a rename at the same wire number:

- MessageType_ZcashTransparentSig     = 1307 [(wire_out) = true];
+ MessageType_ZcashTransparentSigned  = 1307 [(wire_out) = true];

Identical wire value, so nothing on the wire breaks. The other deletions are in messages-zcash.proto (+111/−33), restructuring messages for a chain that does not ship in the 7.14.x line.

Where the volume actually is

+3,071/−112 across 22 files sounds large; only ~750 lines are real .proto/.options. The rest:

lines
generated lib/messages-ripple_pb.js + .d.ts 1,604
package-lock.json + yarn.lock 501
.proto / .optionsthe actual review surface ~750

Worth checking

  • new message numbers do not collide: hive 1614-1617, near, and the solana/ethereum additions
  • supports_taproot in messages.proto — this one is load-bearing for the upcoming 7.14.3 bitcoin-only release, which cannot report the capability without it
  • messages-zcash.proto restructuring, if you want to be thorough — but it cannot affect 7.14.x

Why it matters now

Two firmware releases are waiting on this pin becoming resolvable on master:

  • 7.14.3 bitcoin-only + taproot — currently has to carry a dress-rehearsal pin at this PR's head to get supports_taproot
  • 7.15 — same, via keepkey/keepkey-firmware staging

Both ci/circleci: build and validate are green.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds firmware 7.15 protocol surfaces across Hive, Zcash, clearsigning, and several chains, plus validation and binding-generation updates.

Changes:

  • Adds Hive, NEAR, Zcash, Ethereum/Solana clearsigning, Ripple, and THORChain protocol fields.
  • Updates JavaScript protobuf generation and dependencies.
  • Adds protocol CI and Zcash contract validation.

Reviewed changes

Copilot reviewed 19 out of 22 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
yarn.lock Updates generator dependencies.
types.proto Adds dice-roll button type.
tools/check_zcash_contract.py Validates the RC18 Zcash contract.
package.json Updates protobuf builds and inputs.
package-lock.json Locks updated npm dependencies.
messages.proto Registers messages and common fields.
messages-zcash.proto Defines revised Zcash signing flows.
messages-zcash.options Bounds Zcash fields.
messages-thorchain.proto Adds send denomination.
messages-solana.proto Adds clearsigning attestations.
messages-solana.options Bounds new Solana fields.
messages-ripple.proto Adds transaction memo.
messages-near.proto Defines NEAR protocol messages.
messages-hive.proto Defines Hive protocol messages.
messages-hive.options Bounds Hive fields.
messages-ethereum.proto Adds clearsign and EIP-712 messages.
messages-ethereum.options Bounds new Ethereum fields.
lib/messages-ripple_pb.js Adds generated Ripple bindings.
lib/messages-ripple_pb.d.ts Adds Ripple TypeScript declarations.
.github/workflows/copilot-review.yml Supports cross-fork review requests.
.github/workflows/ci.yml Adds protocol validation CI.
.circleci/config.yml Updates the Node.js image.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/ci.yml
Comment thread messages-solana.options
Comment thread messages-zcash.proto
Comment thread messages.proto Outdated
Comment thread messages-hive.proto Outdated
- ci: include messages-near.proto in the descriptor-validation protoc run so
  the shipped NEAR schema is actually compiled, matching both package builds
- solana: bound the KKSOLSW1 LUT fields (lut_account, lut_signature) and the
  SolanaTokenInfo attestation signature in messages-solana.options, so nanopb
  emits fixed-size fields instead of callbacks
- zcash: document the Zcash wire v2 breaking migration at the top of
  messages-zcash.proto (1307 rename and reshape, transparent output/ack
  phase, ZcashDisplayAddress 3-6 removal, compact signatures, sapling_digest)
- near: mark 1610-1613 and messages-near.proto as schema-only and not
  implemented by firmware 7.15/RC18, add messages-near.options, and move the
  Hive 1614-1617 IDs out from under the NEAR comment
- hive: drop the duplicated legacy paragraph on HiveSignAccountCreate that
  contradicted the attestation contract

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 23 changed files in this pull request and generated 1 comment.

Suppressed comments (5)

Previously missed (4) — in code that hasn't changed since the last review.

.github/workflows/ci.yml:36

  • This only checks the prepare lifecycle's build:js output. It never runs the modified build:json command or the prepublishOnly build path, so CI can remain green even when the publishable package cannot produce lib/proto.json or complete post-processing. Run the full npm run build in CI and validate its outputs; the current BSD-style sed -i '' postprocess also needs to be made Linux-compatible for this runner.
      - name: Verify install generated JavaScript bindings
        run: |
          test -s lib/messages_pb.js
          test -s lib/messages-solana_pb.js
          test -s lib/messages-zcash_pb.js

messages-zcash.proto:77

  • The linked RC18 firmware hard-caps Orchard actions at 16 (ZCASH_MAX_ACTIONS), but this public request field documents no limit and messages-zcash.options still allocates up to 64 returned signatures. A host following these bindings can construct a 17–64 action request that the release rejects, while nanopb consumers generated here allocate a different response shape. Document the 16-action limit and align the options bound with the released implementation.
    optional uint32 n_actions = 4;              // Number of Orchard actions to stream and validate

messages-solana.proto:44

  • This documentation does not match the linked RC18 firmware: when no signature is supplied, firmware still uses a safe unsigned symbol for amount formatting while displaying the authenticated mint separately; only a present-but-invalid signature causes the symbol to be ignored. Document the actual trust/display behavior so host implementers do not incorrectly assume unsigned symbols are never shown.
    // Optional attestation: ECDSA(secp256k1) signature over a domain-separated
    // digest of (mint, decimals, symbol), signed by a clear-sign signer the
    // user loaded via LoadClearsignSigner. When present and valid, the device
    // trusts the symbol; when absent it falls back to displaying the raw mint.
    // (Firmware verifies; the host/SDK signing side is a follow-up.)

messages.proto:113

  • This introduces a new multi-message structured EIP-712 signing API, but the PR description does not mention or validate it. The diff also adds clearsign-attestor message types, Solana attestation/schema fields, taproot capability signaling, and dice-entropy/debug fields outside the stated Hive/clearsign-icon/THORChain/Ripple/Zcash scope. Update the PR scope and validation evidence so these security-sensitive signing surfaces receive explicit review rather than being treated as incidental release additions.
  MessageType_EthereumSignTypedData = 1704 [ (wire_in) = true ];
  MessageType_EthereumTypedDataStructRequest = 1705 [ (wire_out) = true ];
  MessageType_EthereumTypedDataStructAck = 1706 [ (wire_in) = true ];
  MessageType_EthereumTypedDataValueRequest = 1707 [ (wire_out) = true ];
  MessageType_EthereumTypedDataValueAck = 1708 [ (wire_in) = true ];

messages-hive.proto:122

  • The request contract immediately above says this result is proof-of-control attestation material and is not broadcastable, but this response description again calls it a signed account-create transaction. That wording can lead consumers to submit the returned bytes as an on-chain transaction.
 * Response: Signed Hive account_create transaction

Comment thread messages-near.proto Outdated
The Ed25519 signature covers only SHA256(raw_tx), so the host-supplied
receiver_id and action_display were never bound to the bytes being signed.
A malicious host could display a benign receiver and action while obtaining
a signature over entirely different transaction bytes.

Reserve tags 4 and 5 so no implementation can reintroduce them, and state
the confirmation contract on the message: every value shown to the user is
derived by the device from raw_tx, and a transaction the device cannot parse
must be refused rather than displayed from host-provided text. A Borsh
NearTransaction already carries signer_id, public_key, nonce, receiver_id,
block_hash and the action list inside the signed bytes.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 23 changed files in this pull request and generated no new comments.

Suppressed comments (8)

Previously missed (6) — in code that hasn't changed since the last review.

package.json:13

  • The new generator emits a multiline bootstrap containing return Function('return this')() (see the generated Ripple binding), while build:postprocess still matches only the old var global = Function(...) form. The replacement therefore no longer runs, leaving the dynamic-function fallback in published bindings. Update the postprocessor to match the new output.
    "build:js": "mkdir -p ./lib && ./node_modules/.bin/grpc_tools_node_protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --js_out=import_style=commonjs,binary:./lib --ts_out=./lib types.proto messages.proto messages-ethereum.proto messages-eos.proto messages-nano.proto messages-cosmos.proto messages-binance.proto messages-ripple.proto messages-tendermint.proto messages-thorchain.proto messages-osmosis.proto messages-mayachain.proto messages-solana.proto messages-tron.proto messages-ton.proto messages-zcash.proto messages-hive.proto messages-near.proto",

package.json:14

  • prepare runs for Git-based installs, but it generates only JavaScript. Because lib/ is gitignored and proto.json is produced only by build:json, those installs omit the JSON descriptor that published packages contain. Generate both artifact sets here.
    "prepare": "npm run build:js",

messages.proto:113

  • This introduces a separate five-message structured EIP-712 signing protocol that is absent from the PR title, additions list, and validation summary. The same diff also adds attestor, Solana-attestation, dice-entropy, taproot, and Ironwood surfaces. These security-sensitive public APIs materially exceed the described 7.15 scope; update the PR description and validation plan or split the unrelated surfaces so reviewers can assess the actual release contract.
  MessageType_EthereumSignTypedData = 1704 [ (wire_in) = true ];
  MessageType_EthereumTypedDataStructRequest = 1705 [ (wire_out) = true ];
  MessageType_EthereumTypedDataStructAck = 1706 [ (wire_in) = true ];
  MessageType_EthereumTypedDataValueRequest = 1707 [ (wire_out) = true ];
  MessageType_EthereumTypedDataValueAck = 1708 [ (wire_in) = true ];

messages-zcash.proto:99

  • Fields 19–20 advertise Ironwood/transaction-v6 selection, but the stated RC18 contract is Orchard-only and the linked exact firmware candidate's Zcash request metadata and digest API contain neither shielded_pool nor ironwood_digest. A host selecting Ironwood therefore cannot receive the behavior this schema implies. Remove/reserve these fields until firmware support exists, or explicitly mark them schema-only/rejected and pin that behavior in the contract check.
    // NU6.3 / transaction-v6 Orchard-family pool selection. The existing
    // orchard_* metadata fields describe the selected action bundle for wire
    // compatibility; ironwood_digest is the fifth v6 transaction component.
    optional ZcashShieldedPool shielded_pool = 19 [default = ZCASH_SHIELDED_POOL_ORCHARD];
    optional bytes ironwood_digest = 20;        // 32-byte Ironwood component digest (v6)

messages-hive.options:16

  • Nanopb max_size for strings includes the terminating NUL, so these values accept only 15-character names. Hive permits 16-character account names; valid senders or recipients at that limit cannot be encoded by the generated firmware structs.

This issue also appears in the following locations of the same file:

  • line 26
  • line 39
HiveSignTx.from                         max_size:16
HiveSignTx.to                           max_size:16

lib/messages-ripple_pb.js:1

  • lib/ is explicitly ignored as generated output (.gitignore:2) and is regenerated by package.json:11. Committing only this Ripple artifact creates a second, stale-prone source of truth; remove it from the PR and generate bindings during packaging/install.
// source: messages-ripple.proto

messages-hive.options:27

  • These account-name buffers also allow only 15 characters because the configured size includes the NUL terminator. A valid 16-character creator or new account name is rejected/truncated; allocate 17 bytes.
HiveSignAccountCreate.creator           max_size:16
HiveSignAccountCreate.new_account_name  max_size:16

messages-hive.options:39

  • This buffer cannot represent Hive's valid 16-character account names because max_size:16 includes the trailing NUL. Increase it to 17.
HiveSignAccountUpdate.account           max_size:16

Hive account names are 3-16 characters (Graphene fixed_string_16), but nanopb
max_size includes the NUL terminator, so max_size:16 only admits 15 characters.
A valid 16-character account name was truncated or rejected by the generated
firmware structs. Size the five account-name fields at 17: HiveSignTx.from,
HiveSignTx.to, HiveSignAccountCreate.creator/new_account_name, and
HiveSignAccountUpdate.account.

RC18 is Orchard-only, but ZcashSignPCZT tags 19-20 advertised Ironwood /
transaction-v6 pool selection that no firmware in this release implements, so a
host selecting Ironwood could not receive the behavior the schema implied. Mark
ZCASH_SHIELDED_POOL_IRONWOOD and ironwood_digest SCHEMA ONLY on their
declarations, document that 7.15/RC18 rejects a non-Orchard pool and a present
ironwood_digest with Failure, and pin both markers plus the field declarations
in tools/check_zcash_contract.py so the schema-only status cannot silently
drift into an implied guarantee.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants